//==================================================================
//  Copyright (C) 2006  Davide Pasca
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//==================================================================
///
///
///
///
///
//==================================================================

#include "stdafx.h"
#include <string.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "m5d_scene.h"
#include "psys.h"

//==================================================================
using namespace M5D;

//==================================================================
Scene::Scene( const char *namep, const TCHAR *dirpathp ) :
	_modelp(NULL),
	_flshmate_cur_model_idx(0),
	_hint_freed_materials(false),
	_fog_enabled(false),
	_fog_color(0.3f,0.3f,0.3f),
	_fog_start(200.0f),
	_fog_end(900.0f)
{
	_flags = 0;

	if ( dirpathp )
		_tcscpy_s( _load_data_path, dirpathp );
	else
		_load_data_path[0] = 0;

	_modelp = new Model( Model::TYPE_ROOT, this, namep );
	try {
		_models.push_back( _modelp );
	} catch (...) {
		SAFE_DELETE( _modelp );
		throw;
	}
	_modelp->_mod_scenep = this;
}

//==================================================================
Scene::~Scene()
{
	for (int i=0; i < _materials.size(); ++i)
		delete _materials[i];

	for (int i=0; i < _textures.size(); ++i)	
		delete _textures[i];

	for (int i=0; i < _models.size(); ++i)
		delete _models[i];

//	delete _modelp;
}

//==================================================================
void Scene::SetName( const char *namep )
{
	_modelp->SetName( namep );
}

#ifdef M5D_USE_QUAT
//==================================================================
static void quat_normalize( m5d_vector v )
{
/*
	vec_load( v );
	vec_sqr();
	vec_sum();
	vec_rsqrtq();

	vec_load( v );
	vec_scaleq();
	vec_store( v );
*/
}
//==================================================================
static void quat_mul( m5d_vector c, m5d_vector a, m5d_vector b)
{
m5d_vector	t;

	t[0] = a[3]*b[0] + a[0]*b[3] + a[1]*b[2] - a[2]*b[1];
	t[1] = a[3]*b[1] + a[1]*b[3] + a[2]*b[0] - a[0]*b[2];
	t[2] = a[3]*b[2] + a[2]*b[3] + a[0]*b[1] - a[1]*b[0];
	t[3] = a[3]*b[3] - a[0]*b[0] - a[1]*b[1] - a[2]*b[2];

	c[0] = t[0];
	c[1] = t[1];
	c[2] = t[2];
	c[3] = t[3];
}
#endif
/*
//==================================================================
/*
void sce_keyframes_build( int accumulate )
{
int				i, j, k;
Model	*op;
m5d_rotkey_t	*rp;

	op = _models;
	if ( accumulate )
	{
		for (i=0; i < _models.size(); ++i, ++op)
		{
	#ifdef M5D_USE_QUAT
		m5d_vector	acc_quat;

			for (j=0; j < _tnodes_n; ++j)
			{
				acc_quat[0] = 0;
				acc_quat[1] = 0;
				acc_quat[2] = 0;
				acc_quat[3] = 1;
				rp = _tnodesp[j].rotkeysp;
				for (k=_tnodesp[j].n_rotkeys; k; --k, ++rp)
				{
					quat_mul( acc_quat, rp->rot_vec, acc_quat );
					//quat_normalize( acc_quat );
					rp->base_q[0] = acc_quat[0];
					rp->base_q[1] = acc_quat[1];
					rp->base_q[2] = acc_quat[2];
					rp->base_q[3] = acc_quat[3];
				}
			}
	#else
		Matrix44	accmat, mr;

			for (j=0; j < _bones_n; ++j)
			{
				identity( accmat );
				rp = _tnodesp[j].rotkeysp;
				for (k=_tnodesp[j].n_rotkeys; k; --k, ++rp)
				{
					memcpy( rp->base_m, accmat, sizeof(float)*16 );

					set_rotate( mr, rp->rot_vec[3], rp->rot_vec[0], rp->rot_vec[1], rp->rot_vec[2] );
					multiply( accmat, mr, accmat );
				}
			}
	#endif
		}
	}
	else
	{
	#ifdef M5D_USE_QUAT
		for (j=0; j < _tnodes_n; ++j)
		{
			rp = _tnodesp[j].rotkeysp;
			for (k=_tnodesp[j].n_rotkeys; k; --k, ++rp)
			{
				rp->base_q[0] = rp->rot_vec[0];
				rp->base_q[1] = rp->rot_vec[1];
				rp->base_q[2] = rp->rot_vec[2];
				rp->base_q[3] = rp->rot_vec[3];
			}
		}
	#endif
	}
}
*/

//==================================================================
Model *Scene::NewModel( Model::Type type, const char *namep, M5D::Model *parentp, bool serializing ) throw(...)
{
	Model	*modelp = new Model( type, this, namep );
	try {
		_models.push_back( modelp );
	} catch (...) {
		delete modelp;
		throw "NewModel() failed.";
	}

	modelp->_mod_scenep = this;
	if ( serializing )
	{
		modelp->_mod_flags |= Model::FLG_EXISTED_IS_SERIALIZED;
		modelp->_mod_parentp = parentp;
	}
	else
	{
		if ( parentp )	parentp->AddChild( modelp );
		else			GetRootModel()->AddChild( modelp );
	}

	return modelp;
}

//==================================================================
Model *Scene::NewModelCopy( Model *copy_fromp, Model::CopyParams *copy_paramsp, const char *namep, M5D::Model *parentp ) throw(...)
{
	char	buff[Model::NAME_MAXLEN];
	if NOT( namep )
	{
		strcpy_s( buff, copy_fromp->GetName() );
		//strcat( buff, "'" );	// attach ' at the end of a copy
		namep = buff;
	}

	switch ( copy_fromp->GetType() )
	{
	case Model::TYPE_NULL:	return NewModel( copy_fromp->GetType(), namep, parentp );
	case Model::TYPE_MESH:	return NewMeshCopy( namep, (Mesh *)copy_fromp, copy_paramsp, parentp );
	case Model::TYPE_PNTSPRT:	return NewPntSprtCopy( namep, (PntSprt *)copy_fromp, parentp );
	case Model::TYPE_CAMERA:	return NewCameraCopy( namep, (Camera *)copy_fromp, parentp );
	case Model::TYPE_LIGHT:	return NewLightCopy( namep, (Light *)copy_fromp, parentp );
	}

	PASSERT( 0 );
	throw "Copy what ?!";
}

//==================================================================
Model *Scene::NewModelCopyRec( Model *copy_fromp, Model::CopyParams *copy_paramsp, const char *namep, M5D::Model *parentp ) throw(...)
{
	// copy the topmost model, with the specified name and attaching it to the proper parent
	Model	*modelp = NewModelCopy( copy_fromp, copy_paramsp, namep, parentp );

	if ( copy_fromp->_mod_childp )
		newModelCopyRec_Siblings( copy_fromp->_mod_childp, copy_paramsp, modelp );
	
	return modelp;
}

//==================================================================
void Scene::newModelCopyRec_Siblings( Model *src_modelp, Model::CopyParams *copy_paramsp, Model *des_parent_modelp ) throw(...)
{
	Model	*des_modelp = NewModelCopy( src_modelp, copy_paramsp, NULL, des_parent_modelp );

	if ( src_modelp->_mod_nextp )
		newModelCopyRec_Siblings( src_modelp->_mod_nextp, copy_paramsp, des_parent_modelp );

	if ( src_modelp->_mod_childp )
		newModelCopyRec_Siblings( src_modelp->_mod_childp, copy_paramsp, des_modelp );
}

//==================================================================
Model *Scene::FindModel( Model::Type type, const char *namep ) const
{
	for (int i=0; i < _models.size(); ++i)
		if ( _models[i]->_mod_type == type )
			if ( _stricmp( _models[i]->_mod_name, namep ) == 0 )
				return _models[i];

	return NULL;
}

//==================================================================
void Scene::unlinkModel( Model *modelp )
{
	for (int i=0; i < _models.size(); ++i)
	{
		if ( _models[i] == modelp )
		{
			// make sure that the scanning for used materials doesn't
			// ignore any meshes
			if ( i < _flshmate_cur_model_idx )
				_flshmate_cur_model_idx -= 1;

			_models.erase( _models.begin() + i );
			OnModelDelete( modelp );

			return;
		}
	}
}

//==================================================================
void Scene::DeleteModelRec( M5D::Model *modelp )
{
	if ERR_NULL( modelp )
		return;

	if ( modelp->_mod_childp )
		deleteModelRec_Siblings( modelp->_mod_childp );

	delete modelp;
}

//==================================================================
void Scene::deleteModelRec_Siblings( M5D::Model *modelp )
{
	if ( modelp->_mod_nextp )
		deleteModelRec_Siblings( modelp->_mod_nextp );

	if ( modelp->_mod_childp )
		deleteModelRec_Siblings( modelp->_mod_childp );

	delete modelp;
}

//==================================================================
void Scene::TransformGeometryRec( Model *modelp,
								  const Matrix44 &trmat,
								  const Matrix44 *normal_mp )
{
	switch ( modelp->GetType() )
	{
	case Model::TYPE_MESH:
		((Mesh *)modelp)->TransformGeometry( trmat, normal_mp );
		break;
	}

	if ( modelp->_mod_nextp )
		TransformGeometryRec( modelp->_mod_nextp, trmat, normal_mp );

	if ( modelp->_mod_childp )
		TransformGeometryRec( modelp->_mod_childp, trmat, normal_mp );
}

//==================================================================
int Scene::FindModelIdx( Model *p ) const
{
	if ( p == _modelp )
		return -1;

	for (int i=0; i < _models.size(); ++i)
		if ( _models[i] == p )
			return i;

	return M5D_VOID_INDEX;
}

//==================================================================
Mesh *Scene::NewMeshCopy( const char *namep, Mesh *copy_fromp, Model::CopyParams *copy_paramsp, M5D::Model *parentp, bool serializing ) throw(...)
{
	Mesh	*meshp = new Mesh( this, namep, copy_fromp, copy_paramsp );

	// $$$ not deleting object on exception etc etc
	_models.push_back( (Model *)meshp );

	if ( serializing )
	{
		meshp->_mod_flags |= Model::FLG_EXISTED_IS_SERIALIZED;
		meshp->_mod_parentp = parentp;
	}
	else
	{
		if ( parentp )	parentp->AddChild( meshp );
		else			GetRootModel()->AddChild( meshp );
	}

	OnModelCreate( meshp );

	return meshp;
}

//==================================================================
Light *Scene::NewLightCopy( const char *namep, Light *copy_fromp, M5D::Model *parentp, bool serializing ) throw(...)
{
	Light	*lightp = new Light( this, namep, copy_fromp );

	// $$$ not deleting object on exception etc etc
	_models.push_back( (Model *)lightp );

	if ( serializing )
	{
		lightp->_mod_flags |= Model::FLG_EXISTED_IS_SERIALIZED;
		lightp->_mod_parentp = parentp;
	}
	else
	{
		if ( parentp )	parentp->AddChild( lightp );
		else			GetRootModel()->AddChild( lightp );
	}

	OnModelCreate( lightp );

	return lightp;
}

//==================================================================
Camera *Scene::NewCameraCopy( const char *namep, Camera *copy_fromp, M5D::Model *parentp, bool serializing ) throw(...)
{
	Camera	*camerap = new Camera( this, namep, copy_fromp );
	// $$$ not deleting object on exception etc etc
	_models.push_back( (Model *)camerap );

	if ( serializing )
	{
		camerap->_mod_flags |= Model::FLG_EXISTED_IS_SERIALIZED;
		camerap->_mod_parentp = parentp;
	}
	else
	{
		if ( parentp )	parentp->AddChild( camerap );
		else			GetRootModel()->AddChild( camerap );
	}

	OnModelCreate( camerap );

	return camerap;
}

//==================================================================
PntSprt *Scene::NewPntSprt( const char *namep, int max_verts, bool has_color, M5D::Model *parentp, bool serializing ) throw(...)
{
	PntSprt	*pntsprtp;
	pntsprtp = new PntSprt( this, namep, max_verts, has_color );
	// $$$ not deleting object on exception etc etc
	_models.push_back( (Model*)pntsprtp );

	pntsprtp->_mod_scenep = this;
	if ( serializing )
	{
		pntsprtp->_mod_flags |= Model::FLG_EXISTED_IS_SERIALIZED;
		pntsprtp->_mod_parentp = parentp;
	}
	else
	{
		if ( parentp )	parentp->AddChild( pntsprtp );
		else			GetRootModel()->AddChild( pntsprtp );
	}

	OnModelCreate( pntsprtp );

	return pntsprtp;
}

//==================================================================
PntSprt *Scene::NewPntSprtCopy( const char *namep, PntSprt *copy_fromp, M5D::Model *parentp, bool serializing ) throw(...)
{
	PASSERT( 0 );
	throw "woooooo ! Not supported yet !";
}

//==================================================================
DispList *Scene::NewDispList( PackVert::Type packvert_type, int max_verts, u_int max_prim_indices0 ) throw(...)
{
	DispList	*p = new DispList( this, packvert_type, max_verts, max_prim_indices0 );

	OnDispListCreate( p );

	return p;
}

//==================================================================
DispList *Scene::NewDispListCopy( const DispList *src_displistp ) throw(...)
{
	DispList	*p = new DispList( this, src_displistp );

	OnDispListCreate( p );

	return p;
}

//==================================================================
//==================================================================
Material *Scene::NewMaterial( const char *namep ) throw(...)
{
	Material	*p = new Material( this, namep );
	
	// $$$ not deleting object on exception etc etc
	_materials.push_back( p );

	OnMaterialCreate( p );

	return p;
}

//==================================================================
Material *Scene::FindMaterial( const char *namep ) const
{
	for (int i=0; i < _materials.size(); ++i)
		if ( _stricmp( _materials[i]->_name, namep ) == 0 )
			return _materials[i];

	return NULL;
}

//==================================================================
int Scene::FindMaterialIdx( Material *p ) const
{
	for (int i=0; i < _materials.size(); ++i)
		if ( _materials[i] == p )
			return i;

	PASSERT( 0 );
	return M5D_VOID_INDEX;
}

//==================================================================
void Scene::deleteMaterial( Material *matep )
{
	int	i = FindMaterialIdx( matep );
	if ( i >= 0 )
	{
		PDEBUG_PRINTF( _T("deleting material -%s-\n"), matep->_name );
		_materials.erase( _materials.begin() + i );

		delete matep;
	}
	else
	{
		PASSERT( 0 );
	}
}

//==================================================================
Texture *Scene::NewTexture( const char *namep,
							const TCHAR *filenamep,
							ImageBase::LoadParams *load_paramsp ) throw(...)
{
	Texture	*p = new Texture( this, namep, filenamep, load_paramsp );
	// $$$ not deleting object on exception etc etc
	_textures.push_back( p );

	OnTextureCreate( p );

	return p;
}

//==================================================================
Texture *Scene::NewTexture( const char *namep, int w, int h, ImageBase::BaseFormat format ) throw(...)
{
	Texture	*p = new Texture( this, namep, w, h, format );
	// $$$ not deleting object on exception etc etc
	_textures.push_back( p );

	OnTextureCreate( p );

	return p;
}

//==================================================================
Texture *Scene::NewTexture( const char *namep,
							PUtils::Memfile &mf,
							const TCHAR *filenamep,
							PUtils::ImageBase::LoadParams *load_paramsp ) throw(...)
{
	Texture	*p = new Texture( this, namep, mf, filenamep, load_paramsp );
	// $$$ not deleting object on exception etc etc
	_textures.push_back( p );

	OnTextureCreate( p );

	return p;
}

//==================================================================
Texture *Scene::FindTexture( const char *namep ) const
{
	for (int i=0; i < _textures.size(); ++i)
		if ( _stricmp( _textures[i]->_name, namep ) == 0 )
			return _textures[i];

	return NULL;
}

//==================================================================
int Scene::FindTextureIdx( Texture *p ) const
{
	for (int i=0; i < _textures.size(); ++i)
		if ( _textures[i] == p )
			return i;

	return M5D_VOID_INDEX;
}

//==================================================================
void Scene::unlinkTexture( Texture *texp )
{
	int	i = FindTextureIdx( texp );
	if ( i >= 0 )
	{
		_textures.erase( _textures.begin() + i );

		OnTextureDelete( texp );
		//delete texp;
	}
}

//==================================================================
void Scene::idleFlushUnusedMaterials()
{
//	if NOT( _hint_freed_materials )
//		return;
/*
	if ( _flshmate_cur_model_idx == -1 )
	{
		for (int i=0; i < _materials.size(); ++i)
		{
			_materials[i]->_flags &= ~Material::FLG_TMPMARK1;
		}
	}
*/
	bool	reached_bottom = false;

	int	end_model = _flshmate_cur_model_idx + 20;
	if ( end_model >= _models.size() )
	{
		reached_bottom = true;
		end_model = _models.size();
	}

	for (int i=_flshmate_cur_model_idx; i < end_model; ++i)
	{
		if ( _models[i]->_mod_type == Model::TYPE_MESH )
		{
			Mesh	*meshp = (Mesh *)_models[i];

			meshp->markMaterials( Material::FLG_TMPMARK1 );
		}
		else
		if ( _models[i]->_mod_type == Model::TYPE_PNTSPRT )
		{
			PntSprt	*pntsprtp = (PntSprt *)_models[i];

			if ( pntsprtp->GetMaterial() )
				pntsprtp->GetMaterial()->_flags |= Material::FLG_TMPMARK1;
		}
	}
	_flshmate_cur_model_idx = end_model;

	if ( reached_bottom )
	{
		int	i = 0;
		for (i=0; i < _materials.size(); ++i)
		{
			if ( (_materials[i]->_flags & (Material::FLG_CHECK_FOR_DELETION | Material::FLG_TMPMARK1)) == 
				 (Material::FLG_CHECK_FOR_DELETION | Material::FLG_TMPMARK1) )
			{
				deleteMaterial( _materials[i] );
				--i;
			}
			else
				_materials[i]->_flags &= ~(Material::FLG_TMPMARK1 | Material::FLG_CHECK_FOR_DELETION);
		}

		_materials.resize( i );

		_flshmate_cur_model_idx = 0;
	}

//	_hint_freed_materials = false;
}

//==================================================================
void Scene::UpdatePose()
{
	idleFlushUnusedMaterials();

	_modelp->UpdateGlobalTransform();
}

//==================================================================
void Scene::CalcBBox( Vector3 *box_minp, Vector3 *box_maxp )
{
	if ( _models.size() <= 0 )
	{
		box_minp->Set( 0, 0, 0 );
		box_maxp->Set( 0, 0, 0 );
		return;
	}

	box_minp->SetMaxValue();
	box_maxp->SetMinValue();

	for (int i=0; i < _models.size(); ++i)
	{
		if ( _models[i]->_mod_type == Model::TYPE_MESH )
		{
			Mesh	*meshp = (Mesh *)_models[i];

			box_minp->SetMin( meshp->GetBBoxMin() );
			box_maxp->SetMax( meshp->GetBBoxMax() );
		}
	}
}

//==================================================================
int Scene::CountTriangles() const
{
	int	cnt = 0;

	for (int i=0; i < _models.size(); ++i)
		if ( _models[i]->_mod_type == Model::TYPE_MESH )
		{
			Mesh	*meshp = (Mesh *)_models[i];

			cnt += meshp->GetTrigs()._list.size();
		}

	return cnt;
}

//==================================================================
void Scene::GetStats( Stats &stats )
{
	stats._n_models		=	stats._n_visible_models = 0;
	stats._n_meshes		=	stats._n_visible_meshes = 0;
	stats._n_pntsprts	=	stats._n_visible_pntsprts = 0;

	stats._n_models = _models.size();

	for (int i=0; i < stats._n_models; ++i)
	{
		Model	*modelp = _models[i];

		if ( modelp->IsVisible() )
		{
			++stats._n_visible_models;
		}

		switch ( modelp->GetType() )
		{
		case Model::TYPE_MESH:
			++stats._n_meshes;
			break;

		case Model::TYPE_PNTSPRT:
			++stats._n_pntsprts;
			break;
		}
	}

}
